package com.googlecode.kipler.container.generic;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.googlecode.kipler.common.Copyable;

/**
 * The {@link HashMap} based implementation of {@link MultivalueMap}.
 * 
 * @author İnanç Seylan
 */
public class MultivalueHashMap<K, V> implements MultivalueMap<K, V>,
		Copyable<MultivalueHashMap<K, V>> {

	private int valueSize;

	private Map<K, Set<V>> data = new HashMap<K, Set<V>>();

	public void put(K key, V value) {
		if (containsKey(key)) {
			if (!data.get(key).contains(value)) {
				data.get(key).add(value);
				valueSize++;
			}
		} else {
			Set<V> newSet = new HashSet<V>();
			newSet.add(value);
			data.put(key, newSet);
			valueSize++;
		}
	}

	public boolean containsKey(K key) {
		return data.containsKey(key);
	}

	public boolean containsValue(V value) {
		boolean found = false;
		for (K key : data.keySet()) {
			if (data.get(key).contains(value)) {
				found = true;
				break;
			}
		}
		return found;
	}

	public boolean containsKeyValue(K key, V value) {
		return containsKey(key) && data.get(key).contains(value);
	}

	public Set<K> keySet() {
		return data.keySet();
	}

	public void remove(K key) {
		valueSize -= data.get(key).size();
		data.remove(key);
	}

	public void remove(K key, V value) {
		if (containsKey(key)) {
			Set<V> values = data.get(key);
			if (values.contains(value)) {
				valueSize--;
				values.remove(value);
			}
			if (values.isEmpty()) {
				remove(key);
			}
		}
	}

	public Set<V> getValuesForKey(K key) {
		Set<V> result;
		if (containsKey(key)) {
			result = data.get(key);
		} else {
			result = new HashSet<V>();
		}
		return result;
	}

	public Set<V> values() {
		Set<V> result = new HashSet<V>();
		for (K key : keySet())
			result.addAll(getValuesForKey(key));
		return result;
	}

	public MultivalueHashMap<K, V> copy() {
		MultivalueHashMap<K, V> theCopy = new MultivalueHashMap<K, V>();
		for (K key : keySet()) {
			theCopy.data.put(key, new HashSet<V>(getValuesForKey(key)));
		}
		return theCopy;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj != null && (obj.getClass().equals(this.getClass()))) {
			MultivalueHashMap<?, ?> other = (MultivalueHashMap<?, ?>) obj;
			return data.equals(other.data);
		}
		return false;
	}

	@Override
	public int hashCode() {
		return data.hashCode();
	}

	public int keySize() {
		return data.size();
	}

	public int valueSize() {
		return valueSize;
	}

}
